package session

import (
	"net/http"
	"os"

	"chainmaker.org/chainmaker/smarteditor/common"
	"chainmaker.org/chainmaker/smarteditor/conf"
	"chainmaker.org/chainmaker/smarteditor/container"
	"chainmaker.org/chainmaker/smarteditor/generate"
	"chainmaker.org/chainmaker/smarteditor/i18n"
	"chainmaker.org/chainmaker/smarteditor/util"
	"github.com/88250/gulu"
)

// BuildAllContractHandler build all contract 交叉编译出模拟执行的二进制
func BuildAllContractHandler(w http.ResponseWriter, r *http.Request) {
	stopWatcher := util.NewDefaultStopWatcher()

	uid, err := GetUidFromSession(r)
	if err != nil {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)
	if conf.Wide.OpenLimit {
		if !container.SC.SystemResourcePool.Get() {
			log.Warnf("[BuildAllContractHandler]reach resource limit.")
			result.Code = -1
			result.Msg = "resource limit"
			return
		} else {
			defer container.SC.SystemResourcePool.Put()
		}
	}

	stopWatcher.Start("read param")

	requestData := new(invokeContractReqFormData)
	err = util.ReadArgsTo(r.Body, requestData)
	if err != nil {
		result.Code = -1
		log.Errorf("BuildAllContractHandler unmarshal parameter fail. err:%s", err)
		return
	}

	//read uid
	stopWatcher.StopLastAndStartNew("read uid")
	workspace := conf.GetUserWorkspace(uid)
	if workspace == "" {
		result.Code = -1
		log.Errorf("BuildAllContractHandler get workspace fail. err:%s", err)
		return
	}

	//get exec dir name
	stopWatcher.StopLastAndStartNew("get exec dir name")
	execDir := conf.GetExecDirName(workspace)
	if !CanAccess(uid, execDir) {
		result.Code = -1
		log.Errorf("BuildAllContractHandler can not access directory. uid:%s, dir:%s", uid, execDir)
		return
	}

	//clean
	cleanBuildInfoFromCache(uid)

	stopWatcher.StopLastAndStartNew("clean exec dir")
	err = cleanExecDir(execDir)
	if err != nil {
		result.Code = -1
		log.Errorf("BuildAllContractHandler clean directory fail. uid:%s, dir:%s, err:%s", uid, execDir, err)
		return
	}

	//generate code
	stopWatcher.StopLastAndStartNew("generate code")
	selectContractNames := make(map[string]struct{})
	selectContractNames[requestData.ContractName] = struct{}{}

	if requestData.CrossInvoke {
		for _, contractName := range requestData.CrossInvokeContractNameList {
			selectContractNames[contractName] = struct{}{}
		}
	}

	param := generate.Param{
		Workspace:     workspace,
		ContractNames: selectContractNames,
	}

	err = generate.GenCrossContractInvokeCode(param)
	if err != nil {
		result.Code = -1
		// result.Msg = err.Error()
		sendBuildResultToWs(requestData.Sid, err.Error())
		sendBuildErrorToWs(uid, requestData.Sid)
		log.Errorf("BuildAllContractHandler generate contract code fail. uid:%s, dir:%s, err:%s", uid, execDir, err)
		return
	}

	//build
	executableFileName := common.GetExecutableFileName(conf.Docker)
	stopWatcher.StopLastAndStartNew("build contract")
	sendStartBuildToWs(uid, requestData.Sid)
	output, err := buildContract(execDir, executableFileName, conf.Docker)
	if err != nil {
		result.Code = -1
		log.Errorf("BuildAllContractHandler build contract fail. uid:%s, dir:%s, message:%s, err:%s", uid, execDir, output, err)
		//write error message
		sendBuildResultToWs(requestData.Sid, output)
		sendBuildResultToWs(requestData.Sid, err.Error())
		sendBuildErrorToWs(uid, requestData.Sid)
		return
	}

	addBuildInfoToCache(uid, requestData)

	go func() {
		sendBuildSuccessToWs(uid, requestData.Sid)
	}()
}

// cleanExecDir clean exec directory
func cleanExecDir(execDir string) error {
	if err := os.RemoveAll(execDir); err != nil {
		return err
	}
	return os.MkdirAll(execDir, 0775)
}

// buildContract
func buildContract(execDir string, targetFileName string, useDocker bool) (string, error) {
	//执行go get获取指定依赖
	//goGetoutput, err := util.RunGoGetInDirectoryDocker(execDir, useDocker)
	//log.Debugf("BuildContract go get output info. info:%s, err:%v", goGetoutput, err)
	//if err != nil {
	//	return goGetoutput, err
	//}

	goModOutput, err := util.RunGoModTidyInDirectoryDocker(execDir, useDocker, conf.Wide.GoPath, conf.Wide.GoRoot)
	log.Debugf("BuildContract go mod tidy output info. dir:%s, info:%s, err:%v", execDir, goModOutput, err)
	if err != nil {
		return goModOutput, err
	}
	goBuildOutput, err := util.RunGoBuildInDirectoryDocker(execDir, targetFileName, useDocker, conf.Wide.GoPath, conf.Wide.GoRoot)
	log.Debugf("BuildContract go build 230 output info. dir:%s, info:%s, err:%v", execDir, goBuildOutput, err)
	return goBuildOutput, err
}

func sendBuildResultToWs(sid string, data string) {
	defer util.PanicLog()

	channelRetOut := map[string]interface{}{}
	channelRetOut["cmd"] = "build"
	channelRetOut["output"] = data

	wsChannel, err := GetOutputWS(sid)
	if err != nil {
		log.Warnf("send data to ws, channel is nil. sid:%s, data:%s", sid, data)
		return
	}

	err = wsChannel.WriteJSON(&channelRetOut)
	if nil != err {
		log.Warnf("send data to ws,write data fail. sid:%s, data:%s, err:%v", sid, data, err)
	}
	wsChannel.Refresh()
}

func sendStartBuildToWs(uid, sid string) {
	defer util.PanicLog()
	userConfigInfo := conf.GetUser(uid)

	channelRetOut := map[string]interface{}{}

	msg := i18n.Get(userConfigInfo.Locale, "start-build").(string)
	channelRetOut["cmd"] = "start-build"
	channelRetOut["output"] = "<span class='start-build'>" + msg + "</span>\n"

	wsChannel, err := GetOutputWS(sid)
	if err != nil {
		log.Warnf("build all. write start build message to channel, channel is nil")
		return
	}

	err = wsChannel.WriteJSON(&channelRetOut)
	if nil != err {
		log.Warnf("build all. send start build message to ws, write data fail. sid:%s, err:%v", sid, err)
	}
	wsChannel.Refresh()
}

func sendBuildSuccessToWs(uid, sid string) {
	defer util.PanicLog()
	userConfigInfo := conf.GetUser(uid)

	channelRetOut := map[string]interface{}{}
	channelRetOut["cmd"] = "build-success"
	channelRetOut["seq"] = "aa"
	channelRetOut["output"] = "<span class='build-succ'>" +
		i18n.Get(userConfigInfo.Locale, "build-succ").(string) + "</span>\n"

	wsChannel, err := GetOutputWS(sid)
	if err != nil {
		log.Warnf("build all. send build success message to ws, channel is nil. uid:%s, sid:%s", uid, sid)
		return
	}

	err = wsChannel.WriteJSON(&channelRetOut)
	if nil != err {
		log.Warnf("build all. send build success message to ws. uid:%s, sid:%s, err:%v", uid, sid, err)
	}
	wsChannel.Refresh()
}

func sendBuildErrorToWs(uid, sid string) {
	defer util.PanicLog()

	channelRetOut := map[string]interface{}{}
	channelRetOut["cmd"] = "build-failed"
	channelRetOut["output"] = "<span class='stderr'> build-failed </span>"

	wsChannel, err := GetOutputWS(sid)
	if err != nil {
		log.Warnf("build all. send build success message to ws, channel is nil. uid:%s, sid:%s", uid, sid)
		return
	}

	err = wsChannel.WriteJSON(&channelRetOut)
	if nil != err {
		log.Warnf("build all. send build success message to ws. uid:%s, sid:%s, err:%v", uid, sid, err)
	}
	wsChannel.Refresh()
}

func cleanBuildInfoFromCache(uid string) {
	container.SC.BuildContractCache.CleanBuildContractInfo(uid)
}

func addBuildInfoToCache(uid string, requestData *invokeContractReqFormData) {
	contractNames := make([]string, 0)
	contractNames = append(contractNames, requestData.ContractName)
	if requestData.CrossInvoke {
		contractNames = append(contractNames, requestData.CrossInvokeContractNameList...)
	}
	container.SC.BuildContractCache.AddBuildContractInfo(uid, contractNames...)
}
